package pulpfizz.physics;

import java.util.HashSet;

import org.jbox2d.collision.Shape;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.contacts.Contact;
import org.jbox2d.dynamics.contacts.ContactEdge;

/*
 * TODO: usually things need to be refiltered after changing collision filtering
 * characteristics like these. -e
 */

/**
 * Some utility methods for dealing with JBox2d collision filtering.
 * 
 * See http://www.box2d.org/manual.html#d0e845 for more info on collision
 * filtering in box2d.
 * 
 * The basic idea is this: Collision GROUPS have highest priority, and can
 * define groups that either always collide or never collide. Collision
 * CATEGORIES define a shape's category, and take the form of bits stored in an
 * integer format. Collision MASKS define which categories a given shape will
 * collide with.
 * 
 * Categories:
 * 
 * @author Greg
 * 
 */
public class CollisionUtils
{

	static int counter = 2;

	/**
	 * Returns a new category integer, using a static counter to make sure that
	 * the same category isn't doled out twice.
	 * 
	 * @return
	 */
	public static int getNewCategory()
	{
		int returnMe = counter;
		counter *= 2;
		return returnMe;
	}

	/**
	 * Sets the collision category of the given Body object(s). This overrides
	 * any existing category definition.
	 * 
	 * @param categoryBits
	 * @param bodies
	 */
	public static void setCategory(int categoryBits, Body... bodies)
	{
		for (Body b : bodies)
		{
			Shape s = b.getShapeList();
			while (s != null)
			{
				s.m_filter.categoryBits = categoryBits;
				s = s.getNext();
			}
		}
	}

	/**
	 * Sets the collision category of the given Shape object(s). This overrides
	 * any existing category definition.
	 * 
	 * @param categoryBits
	 * @param shapes
	 */
	public static void setCategory(int categoryBits, Shape... shapes)
	{
		for (Shape s : shapes)
		{
			s.m_filter.categoryBits = categoryBits;
		}
	}

	/**
	 * Modifies the mask bits of the given Body object(s) to stop colliding with
	 * the given category type.
	 * 
	 * @param maskBits
	 * @param bodies
	 */
	public static void maskOut(int maskBits, Body... bodies)
	{
		for (Body b : bodies)
		{
			Shape s = b.getShapeList();
			while (s != null)
			{
				s.m_filter.maskBits = s.m_filter.maskBits & ~maskBits;
				s = s.getNext();
			}
		}
	}

	/**
	 * Modifies the mask bits of the given Shape object(s) to stop colliding
	 * with the given category type.
	 * 
	 * @param maskBits
	 * @param shapes
	 */
	public static void maskOut(int maskBits, Shape... shapes)
	{
		for (Shape s : shapes)
		{
			s.m_filter.maskBits = s.m_filter.maskBits & ~maskBits;
		}
	}

	/**
	 * Replaces the maskBits of the given Body object(s) with the given mask.
	 * 
	 * @param maskBits
	 * @param bodies
	 */
	public static void setMask(int maskBits, Body... bodies)
	{
		for (Body b : bodies)
		{
			Shape s = b.getShapeList();
			while (s != null)
			{
				s.m_filter.maskBits = maskBits;
				s = s.getNext();
			}
		}
	}

	/**
	 * Replaces the maskBits of the given Shape object(s) with the given mask.
	 * 
	 * @param maskBits
	 * @param shapes
	 */
	public static void setMask(int maskBits, Shape... shapes)
	{
		for (Shape s : shapes)
		{
			s.m_filter.maskBits = maskBits;
		}
	}

	/**
	 * Sets the collision Group of the given Body object(s).
	 * 
	 * @param group
	 * @param bodies
	 */
	public static void setGroup(int group, Body... bodies)
	{
		for (Body b : bodies)
		{
			Shape s = b.getShapeList();
			while (s != null)
			{
				s.m_filter.groupIndex = group;
				s = s.getNext();
			}
		}
	}

	/**
	 * Sets the collision Group of the given Shape object(s).
	 * 
	 * @param group
	 * @param bodies
	 */
	public static void setGroup(int group, Shape... shapes)
	{
		for (Shape s : shapes)
		{
			s.m_filter.groupIndex = group;
			s = s.getNext();
		}
	}

	/**
	 * Sets whether or not the given Body objects are Sensors or not.
	 * 
	 * @param group
	 * @param bodies
	 */

	public static void setSensor(boolean isSensor, Body... bodies)
	{
		for (Body b : bodies)
		{
			Shape s = b.getShapeList();
			while (s != null)
			{
				s.m_isSensor = isSensor;
				s = s.getNext();
			}
		}
	}

	/**
	 * Sets whether or not the given Body objects are Sensors or not.
	 * 
	 * @param isSensor
	 * @param shapes
	 */

	public static void setSensor(boolean isSensor, Shape... shapes)
	{
		for (Shape s : shapes)
		{
			s.m_isSensor = isSensor;
			s = s.getNext();
		}
	}

	/**
	 * Returns the first body we can find contacting this body.
	 * @param b
	 * @return
	 */
	public static Body findFirstContact(Body b)
	{
		Body contactingBody = null;
		ContactEdge ce = b.m_contactList;
		while (ce != null)
		{
			contactingBody = ce.other;
			break;
		}
		return contactingBody;
	}
	
	
	
	/**
	 * Returns the number of distinct bodies contacting this body.
	 * @param b
	 * @return
	 */
	public static int getContactCount(Body b)
	{
		HashSet<Body> bodies = new HashSet<Body>();
		Shape s;
		ContactEdge ce = b.m_contactList;
		while (ce != null)
		{
			bodies.add(ce.other);
			ce = ce.next;
		}
		return bodies.size();
	}
	
	public static Body[] getContactingBodies(Body b)
	{
		HashSet<Body> bodies = new HashSet<Body>();
		Shape s;
		ContactEdge ce = b.m_contactList;
		while (ce != null)
		{
			bodies.add(ce.other);
			ce = ce.next;
		}
		return bodies.toArray(new Body[]{});
	}
	
	public static Body[] getContactingBodies(Shape s)
	{
		HashSet<Body> bodies = new HashSet<Body>();
		Body b = s.getBody();
		ContactEdge ce = b.m_contactList;
		while (ce != null)
		{
			Contact c = ce.contact;
			if (c.getShape1() == s || c.getShape2() == s)
			{
				bodies.add(ce.other);
			}
			ce = ce.next;
		}
		return bodies.toArray(new Body[bodies.size()]);
	}

}
